
package com.tools.cleanmaster.controller;

import android.annotation.SuppressLint;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.mtp.MtpConstants;
import android.os.Build;
import android.os.Environment;
import android.provider.MediaStore;

import com.tools.cleanmaster.CleanApplication;
import com.tools.cleanmaster.bean.ApkFileInfo;
import com.tools.cleanmaster.manager.ApkFileManager;
import com.tools.cleanmaster.model.TrashType;
import com.tools.cleanmaster.model.item.FileTrashItem;
import com.tools.cleanmaster.model.item.TrashItem;
import com.tools.cleanmaster.utils.FeatureConfig;
import com.tools.cleanmaster.utils.LogHelper;
import com.tools.cleanmaster.utils.MimeTypes;
import com.tools.cleanmaster.utils.TrashMimeTypes;
import com.tools.cleanmaster.utils.TrashUtils;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class FileScanner extends TrashScanner {

    public static final String SQL_SECLECTION_DATA_POSTFIX = "_data like \"%\" || ?";

    public static final String DB_NAME_EXTERNAL = "external";

    public static final int DIRS_SCAN_LEVEL = 1000000;

    private static final int DIRS_SCAN_LEVEL_FOR_ANDROIDDATA = DIRS_SCAN_LEVEL + 3;

    private static final int DIRS_SCAN_LEVEL_FOR_TENCENT = DIRS_SCAN_LEVEL + 3;

    private final ArrayList<TrashType> mTrashTypes = new ArrayList<TrashType>();

    private final String[] mSdCardRootDirs;

    private final FileTrashMatcher mFileMatcher;

    private FileTrashMatcher mDirMatcher;

    private final Map<TrashType, Long> mTypeSizeMap = new HashMap<TrashType, Long>();

    private String mCurRootDir;

    private int mCurScanLev;

    private final String mAndroidDataFilePath;

    private static final String FORMAT = "format";

    private TrashType mThumbNailType = null;

    private abstract class FileTrashMatcher {
        private FileTrashMatcher mParent;

        private List<FileTrashMatcher> mSiblings;

        final void setParentMatcher(FileTrashMatcher parent) {
            mParent = parent;
        }

        final void addSiblingMatcher(FileTrashMatcher matcher) {
            if (mSiblings == null) {
                mSiblings = new ArrayList<FileTrashMatcher>();
            }

            mSiblings.add(matcher);
        }

        abstract TrashItem onMatch(File f);

        final List<TrashItem> match(File f) {
            List<TrashItem> items = null;
            TrashItem item = null;
            if (mParent != null) {
                items = mParent.match(f);
            }

            if ((item = onMatch(f)) != null) {
                if (items == null) {
                    items = new ArrayList<TrashItem>(1);
                }
                items.add(item);
                return items;
            }
            if (mSiblings != null) {
                for (FileTrashMatcher m : mSiblings) {
                    if ((item = m.onMatch(f)) != null) {
                        if (items == null) {
                            items = new ArrayList<TrashItem>(1);
                        }
                        items.add(item);
                        return items;
                    }
                }
            }
            return items;
        }

    }

    private static final int FRIST_LEVEL = 1;

    // directory matcher
    private static final String[] EMPTY_FOLDER_PATH_SKIPPED = {
            "bluetooth", "usbStorage", "Music", "Ringtones", "Alarms", "Notifications", "Pictures",
            "Movies", "Download", "Podcasts", "DCIM"
    };

    private final class EmptyFolderMatcher extends FileTrashMatcher {

        @Override
        TrashItem onMatch(File f) {
            if (mCurScanLev != FRIST_LEVEL) {
                return null;
            }
            String absolutePath = f.getAbsolutePath();
            String[] files = f.list();
            if (files == null || files.length == 0) {
                for (String p : EMPTY_FOLDER_PATH_SKIPPED) {
                    if (absolutePath.equals(new File(mCurRootDir, p).getAbsolutePath())) {
                        return null;
                    }
                }
                return createTrashItemEmptyFolder(absolutePath, f.length());
            }
            return null;
        }

    }

    private static final String[] THUMBNAIL_DIRS_LIST = {
            "/DCIM/.thumbnails", "/Camera/.thumbnails", "/DCIM/Camera/.thumbnails",
            "/DCIM/camera/.thumbnails", "/.thumbnails"
    };

    @SuppressLint("NewApi")
    private void scanThumbnailTrash() {
        ArrayList<String> thumbnailPaths = new ArrayList<String>();
        for (String rootDir : mSdCardRootDirs) {
            for (String thumbNailDir : THUMBNAIL_DIRS_LIST) {
                File thumbFile = new File(rootDir, thumbNailDir);
                if (thumbFile.exists() && thumbFile.isDirectory()) {
                    createTrashItemThumbnail(thumbFile);
                    thumbnailPaths.add(thumbFile.getAbsolutePath());
                }
            }
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
            File dcimDir = Environment
                    .getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
            File file = new File(dcimDir, ".thumbnails");
            if (file.exists() && file.isDirectory()
                    && !thumbnailPaths.contains(file.getAbsolutePath())) {
                createTrashItemThumbnail(file);
            }
        }
        mTrashHandler.onTrashTypeFinish(TrashType.THUMBNAIL,
                mTypeSizeMap.get(TrashType.THUMBNAIL));
    }

    private void createTrashItemThumbnail(File file) {
        TrashItem trash = new TrashItem();
        trash.trashType = TrashType.THUMBNAIL;
        trash.filePath = file.getAbsolutePath();
        trash.isDir = true;
        trash.isSelectedDefault = trash.isSelected = false;
        long[] countAndSize = TrashUtils.getFileCountAndSizeWithDepth(file, TrashUtils.DEPTH_MAX);
        trash.fileCount = (int) countAndSize[0];
        trash.size = countAndSize[1];
        mTypeSizeMap.put(TrashType.THUMBNAIL, mTypeSizeMap.get(TrashType.THUMBNAIL) + trash.size);
        if (!mStopRequested) {
            mTrashesData.putTrash(TrashType.THUMBNAIL, trash);
        }
        if (FeatureConfig.DEBUG_LOG) {
            LogHelper.v(SUB_TAG, "[trash_thumbnail] filePath:" + trash.filePath + " size:"
                    + trash.size);
        }
    }

    // file matcher
    public static final String[] APK_PATH_SKIPPED = {
            ".android_secure", "OpenRecovery", "TitaniumBackup", "TitaniumBackup",
            "AndroidOptimizer/systembackup/"
    };

    private final class ApkMatcher extends FileTrashMatcher {

        @Override
        TrashItem onMatch(File f) {
            String absolutePath = f.getAbsolutePath();
            if (absolutePath.endsWith(".apk")) {
                for (String p : APK_PATH_SKIPPED) {
                    if (absolutePath.startsWith(new File(mCurRootDir, p).getAbsolutePath())) {
                        return null;
                    }
                }
                return createTrashItemApk(f, absolutePath);
            }
            return null;

        }
    }

    private static final String[] TEMP_FILE_PATTERNS = {
            "\\S+\\.tmp", "thumbs\\.db"
    };

    private static final String[] TEMP_PATH_SKIPPED = {
            "KuwoMusic"
    };

    private final class TempMatcher extends FileTrashMatcher {

        @Override
        TrashItem onMatch(File f) {
            for (String p : TEMP_PATH_SKIPPED) {
                if (f.getAbsolutePath().startsWith(new File(mCurRootDir, p).getAbsolutePath())) {
                    return null;
                }
            }
            String fileName = f.getName();
            if (fileName.endsWith(".tmp") || "thumbs.db".equals(fileName)) {
                return createTrashItemTemp(f.getAbsolutePath(), f.length());
            }
            return null;

        }

    }

    private final class LogMatcher extends FileTrashMatcher {

        @Override
        TrashItem onMatch(File f) {
            String path = f.getAbsolutePath();
            if (path.endsWith(".log") || path.endsWith(".LOG")) {
                return createTrashItemLog(path, f.length());
            }
            return null;

        }

    }

    private final String[] mVidioFileExtensions = new String[] {
            ".wmv", ".avi", ".mkv", ".rmvb", ".rm", ".xvid", ".mp4", ".3gp", ".mpg", ".flv",
            ".f4v", ".mpeg4"
    };

    // wmv,avi,mkv,rmvb,rm,xvid,mp4,3gp,mpg,flv ,f4v,mpeg4
    private boolean isMediaFile(String fileName) {
        if (fileName == null) {
            return false;
        }
        String extension = MimeTypes.getExtension(fileName);
        if (extension != null && extension.length() <= 6 && extension.length() >= 3) {
            extension = extension.toLowerCase();
            for (String ext : mVidioFileExtensions) {
                if (ext.equals(extension)) {
                    return true;
                }
            }
        }
        return false;
    }

    public static final long LARGE_FILE_THRESHOLD = 1024 * 1024 * 10; // 10MB

    private final class LargeFileMatcher extends FileTrashMatcher {
        @Override
        TrashItem onMatch(File f) {
            long size = f.length();
            if (size >= LARGE_FILE_THRESHOLD) {
                return createTrashItemLargeFile(f, f.getAbsolutePath(), size, -1);
            }
            return null;
        }
    }

    public FileScanner(Context cxt, TrashHandler hanlder, TrashType t) {
        super(cxt, hanlder);
        mSdCardRootDirs = hanlder.getRootDirs();
        if (mSdCardRootDirs == null || mSdCardRootDirs.length == 0) {
            throw new IllegalArgumentException("None dirs need be scanned");
        }
        // create matcher
        FileTrashMatcher largeMatcher = null;
        List<FileTrashMatcher> fileMatcherList = new ArrayList<FileTrashMatcher>();

        mTypeSizeMap.put(t, 0l);
        mTrashTypes.add(t);
        if (t == TrashType.EMPTY_FOLDER) {
            mDirMatcher = new EmptyFolderMatcher();
        } else if (t == TrashType.THUMBNAIL) {
            mThumbNailType = t;
            mTrashTypes.remove(t);
        } else if (t == TrashType.APK_FILE) {
            fileMatcherList.add(new ApkMatcher());
        } else if (t == TrashType.TEMP_FILE) {
            fileMatcherList.add(new TempMatcher());
        } else if (t == TrashType.LOG_FILE) {
            fileMatcherList.add(new LogMatcher());
        } else if (t == TrashType.LARGE_FILE) {
            largeMatcher = new LargeFileMatcher();
        }

        FileTrashMatcher fileMatcher = null;
        if (!fileMatcherList.isEmpty()) {
            fileMatcher = fileMatcherList.get(0);
            for (int i = 1; i < fileMatcherList.size(); i++) {
                fileMatcher.addSiblingMatcher(fileMatcherList.get(i));
            }
        }

        if (largeMatcher != null) {
            if (fileMatcher == null) {
                fileMatcher = largeMatcher;
            } else {
                fileMatcher.setParentMatcher(largeMatcher);
            }
        }
        mFileMatcher = fileMatcher;
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
            File root = Environment.getExternalStorageDirectory();
            mAndroidDataFilePath = (root.getAbsolutePath() + "/android/data").toLowerCase();
        } else {
            mAndroidDataFilePath = null;
        }
    }

    @Override
    public void scan(int increaseProgress) {
        mIsScanning = true;
        doScan(increaseProgress);
        mIsScanning = false;
    }

    private void doScan(int increaseProgress) {
        long startTime;
        if (FeatureConfig.DEBUG_LOG) {
            LogHelper.i(SUB_TAG, "FileScanner doScan increaseProgress = " + increaseProgress);
            startTime = System.currentTimeMillis();
        }
        // Debug.startMethodTracing("trash", 200 * 1024 * 1024);
        if (mThumbNailType != null) {
            scanThumbnailTrash();
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            if (!scanMediaStore(increaseProgress)) {
                scanRoots(increaseProgress);
            }
        } else {
            scanRoots(increaseProgress);
        }
        // Debug.stopMethodTracing();
        if (FeatureConfig.DEBUG_LOG) {
            LogHelper.i(SUB_TAG, "FileScanner doScan end time = "
                    + (System.currentTimeMillis() - startTime));
        }
    }

    private boolean scanMediaStore(int increaseProgress) {
        if (mStopRequested) {
            if (FeatureConfig.DEBUG_LOG) {
                LogHelper
                        .v(SUB_TAG, "doScan, mStopRequested, have not start,scanMediaStore return");
            }
            return true;
        }
        if (FeatureConfig.DEBUG_LOG) {
            LogHelper.v(SUB_TAG, "doScan, doScanMediaStore begin");
        }
        final int typeCount = mTrashTypes.size();
        for (int i = 0; i < typeCount; i++) {
            int progressType = calculateProgress(increaseProgress, i, typeCount);
            TrashType t = mTrashTypes.get(i);
            if (t == TrashType.EMPTY_FOLDER) {
                scanMediaEmptyFolder(progressType);
            } else if (t == TrashType.APK_FILE) {
                scanMediaApk(progressType);
            } else if (t == TrashType.TEMP_FILE) {
                scanMediaTemp(progressType);
            } else if (t == TrashType.LOG_FILE) {
                scanMediaLog(progressType);
            } else if (t == TrashType.LARGE_FILE) {
                scanMediaLargeFile(progressType);
            } else {
                mTrashHandler.updateProgress(progressType, null);
            }
            mTrashHandler.onTrashTypeFinish(t, mTypeSizeMap.get(t));
        }
        if (FeatureConfig.DEBUG_LOG) {
            LogHelper.v(SUB_TAG, "doScan, doScanMediaStore end!");
        }
        return true;
    }

    @SuppressLint("NewApi")
    private void scanMediaLargeFile(int increaseProgress) {
        Cursor cursor = null;
        long startTime;
        if (FeatureConfig.DEBUG_LOG) {
            startTime = System.currentTimeMillis();
        }
        try {
            ContentResolver contentResolver = CleanApplication.getInstance().getContentResolver();
            cursor = contentResolver.query(MediaStore.Files.getContentUri(DB_NAME_EXTERNAL),
                    new String[] {
                            MediaStore.Files.FileColumns.DATA, MediaStore.Files.FileColumns.SIZE, MediaStore.Files
                            .FileColumns._ID
                    }, MediaStore.Files.FileColumns.SIZE + " >= " + LARGE_FILE_THRESHOLD, null,
                    null);
            if (cursor == null) {
                return;
            }
            int count = cursor.getCount();
            if (FeatureConfig.DEBUG_LOG) {
                LogHelper.v(SUB_TAG, "doScan, scanMediaLargeFile cursor count=" + count);
            }
            if (count == 0) {
                mTrashHandler.updateProgress(increaseProgress, null);
                return;
            }
            int index = 0;
            while (cursor.moveToNext()) {
                if (mStopRequested) {
                    if (FeatureConfig.DEBUG_LOG) {
                        LogHelper.v(SUB_TAG,
                                "doScan, scanMediaLargeFile mStopRequested, in while return");
                    }
                    return;
                }
                String path = cursor.getString(0);
                long size = cursor.getLong(1);
                long id = cursor.getLong(2);
                File file = new File(path);
                if (new File(path).exists()) {
                    createTrashItemLargeFile(file, path, size, id);
                }
                int progressFile = calculateProgress(increaseProgress, index, count);
                mTrashHandler.updateProgress(progressFile, path);
                index++;
            }
        } catch (Exception e) {
            if (FeatureConfig.DEBUG_LOG) {
                LogHelper.e(SUB_TAG, "scanMediaLargeFile", e);
            }
            return;
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
        if (FeatureConfig.DEBUG_LOG) {
            LogHelper.v(SUB_TAG,
                    "doScan, scanMediaLargeFile end! time = "
                            + (System.currentTimeMillis() - startTime));
        }
    }

    @SuppressLint("NewApi")
    private void scanMediaTemp(int increaseProgress) {
        Cursor cursor = null;
        long startTime;
        if (FeatureConfig.DEBUG_LOG) {
            startTime = System.currentTimeMillis();
        }
        try {
            ContentResolver contentResolver = CleanApplication.getInstance().getContentResolver();
            cursor = contentResolver.query(MediaStore.Files.getContentUri(DB_NAME_EXTERNAL),
                    new String[] {
                            MediaStore.Files.FileColumns.DATA, MediaStore.Files.FileColumns.SIZE
                    }, "_data like \"%.tmp\" or _data like \"%\\thumbs.db\"", null, null);
            if (cursor == null) {
                return;
            }
            int count = cursor.getCount();
            if (FeatureConfig.DEBUG_LOG) {
                LogHelper.v(SUB_TAG, "doScan, scanMediaTemp cursor count=" + count);
            }
            if (count == 0) {
                mTrashHandler.updateProgress(increaseProgress, null);
                return;
            }
            int index = 0;
            while (cursor.moveToNext()) {
                if (mStopRequested) {
                    if (FeatureConfig.DEBUG_LOG) {
                        LogHelper.v(SUB_TAG,
                                "doScan, scanMediaTemp mStopRequested, in while return");
                    }
                    return;
                }
                String path = cursor.getString(0);
                long size = cursor.getLong(1);
                File file = new File(path);
                if (file.exists() && file.isFile()) {
                    boolean isSkippedFolder = false;
                    for (String root : mSdCardRootDirs) {
                        for (String p : TEMP_PATH_SKIPPED) {
                            if (path.startsWith(new File(root, p).getAbsolutePath())) {
                                isSkippedFolder = true;
                                break;
                            }
                        }
                        if (isSkippedFolder) {
                            break;
                        }
                    }
                    if (!isSkippedFolder) {
                        createTrashItemTemp(path, size);
                    }
                }
                int progressFile = calculateProgress(increaseProgress, index, count);
                mTrashHandler.updateProgress(progressFile, path);
                index++;
            }
        } catch (Exception e) {
            if (FeatureConfig.DEBUG_LOG) {
                LogHelper.e(SUB_TAG, "scanMediaTemp", e);
            }
            return;
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
        if (FeatureConfig.DEBUG_LOG) {
            LogHelper.v(SUB_TAG, "doScan, scanMediaTemp end! time = "
                    + (System.currentTimeMillis() - startTime));
        }
    }

    @SuppressLint("NewApi")
    private void scanMediaApk(int increaseProgress) {
        Cursor cursor = null;
        long startTime;
        if (FeatureConfig.DEBUG_LOG) {
            startTime = System.currentTimeMillis();
        }
        try {
            ContentResolver contentResolver = CleanApplication.getInstance().getContentResolver();
            cursor = contentResolver.query(MediaStore.Files.getContentUri(DB_NAME_EXTERNAL),
                    new String[] {
                            MediaStore.Files.FileColumns.DATA
                    }, SQL_SECLECTION_DATA_POSTFIX, new String[] {
                            ".apk"
                    }, null);
            if (cursor == null) {
                return;
            }
            int count = cursor.getCount();
            if (FeatureConfig.DEBUG_LOG) {
                LogHelper.v(SUB_TAG, "doScan, scanMediaApk cursor count=" + count);
            }
            if (count == 0) {
                mTrashHandler.updateProgress(increaseProgress, null);
                return;
            }
            int index = 0;
            while (cursor.moveToNext()) {
                if (mStopRequested) {
                    if (FeatureConfig.DEBUG_LOG) {
                        LogHelper
                                .v(SUB_TAG, "doScan, scanMediaApk mStopRequested, in while return");
                    }
                    return;
                }
                String path = cursor.getString(0);
                int progressFile = calculateProgress(increaseProgress, index, count);
                mTrashHandler.updateProgress(progressFile, path);
                index++;
                File file = new File(path);
                if (file.exists()) {
                    boolean isSkippedFolder = false;
                    for (String root : mSdCardRootDirs) {
                        for (String p : APK_PATH_SKIPPED) {
                            if (path.startsWith(new File(root, p).getAbsolutePath())) {
                                isSkippedFolder = true;
                                break;
                            }
                        }
                        if (isSkippedFolder) {
                            break;
                        }
                    }
                    if (!isSkippedFolder) {
                        createTrashItemApk(file, path);
                    }
                }
            }
        } catch (Exception e) {
            if (FeatureConfig.DEBUG_LOG) {
                LogHelper.e(SUB_TAG, "scanMediaLog", e);
            }
            return;
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
        if (FeatureConfig.DEBUG_LOG) {
            LogHelper.v(SUB_TAG, "doScan, scanMediaApk end! time = "
                    + (System.currentTimeMillis() - startTime));
        }
    }

    @SuppressLint("NewApi")
    private void scanMediaEmptyFolder(int increaseProgress) {
        Cursor cursor = null;
        long startTime;
        if (FeatureConfig.DEBUG_LOG) {
            startTime = System.currentTimeMillis();
        }
        try {
            ContentResolver contentResolver = CleanApplication.getInstance().getContentResolver();
            cursor = contentResolver.query(MediaStore.Files.getContentUri(DB_NAME_EXTERNAL),
                    new String[] {
                            MediaStore.Files.FileColumns.DATA
                    }, FORMAT + " = ? and " + MediaStore.Files.FileColumns.PARENT + " = 0",
                    new String[] {
                            Integer.toString(MtpConstants.FORMAT_ASSOCIATION)
                    }, null);
            if (cursor == null) {
                return;
            }
            int count = cursor.getCount();
            if (FeatureConfig.DEBUG_LOG) {
                LogHelper.v(SUB_TAG, "doScan, scanMediaEmptyFolder cursor count=" + count);
            }
            if (count == 0) {
                mTrashHandler.updateProgress(increaseProgress, null);
                return;
            }
            int index = 0;
            while (cursor.moveToNext()) {
                if (mStopRequested) {
                    if (FeatureConfig.DEBUG_LOG) {
                        LogHelper.v(SUB_TAG,
                                "doScan, scanMediaEmptyFolder mStopRequested, in while return");
                    }
                    return;
                }
                String path = cursor.getString(0);
                File file = new File(path);
                if (file.exists()) {
                    String[] list = file.list();
                    if (list == null || list.length == 0) {
                        boolean isSkippedFolder = false;
                        for (String root : mSdCardRootDirs) {
                            for (String p : EMPTY_FOLDER_PATH_SKIPPED) {
                                if (path.equals(new File(root, p).getAbsolutePath())) {
                                    isSkippedFolder = true;
                                    break;
                                }
                            }
                            if (isSkippedFolder) {
                                break;
                            }
                        }
                        if (!isSkippedFolder) {
                            createTrashItemEmptyFolder(path, file.length());
                        }
                    }
                }
                int progressFile = calculateProgress(increaseProgress, index, count);
                mTrashHandler.updateProgress(progressFile, path);
                index++;
            }
        } catch (Exception e) {
            if (FeatureConfig.DEBUG_LOG) {
                LogHelper.e(SUB_TAG, "scanMediaEmptyFolder", e);
            }
            return;
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
        if (FeatureConfig.DEBUG_LOG) {
            LogHelper.v(SUB_TAG,
                    "doScan, scanMediaEmptyFolder end! time = "
                            + (System.currentTimeMillis() - startTime));
        }
    }

    @SuppressLint("NewApi")
    private void scanMediaLog(int increaseProgress) {
        Cursor cursor = null;
        long startTime;
        if (FeatureConfig.DEBUG_LOG) {
            startTime = System.currentTimeMillis();
        }
        try {
            ContentResolver contentResolver = CleanApplication.getInstance().getContentResolver();
            cursor = contentResolver.query(MediaStore.Files.getContentUri(DB_NAME_EXTERNAL),
                    new String[] {
                            MediaStore.Files.FileColumns.DATA, MediaStore.Files.FileColumns.SIZE
                    }, SQL_SECLECTION_DATA_POSTFIX, new String[] {
                            ".log"
                    }, null);
            if (cursor == null) {
                return;
            }
            int count = cursor.getCount();
            if (FeatureConfig.DEBUG_LOG) {
                LogHelper.v(SUB_TAG, "doScan, scanMediaLog cursor count=" + count);
            }
            if (count == 0) {
                mTrashHandler.updateProgress(increaseProgress, null);
                return;
            }
            int index = 0;
            while (cursor.moveToNext()) {
                if (mStopRequested) {
                    if (FeatureConfig.DEBUG_LOG) {
                        LogHelper
                                .v(SUB_TAG, "doScan, scanMediaLog mStopRequested, in while return");
                    }
                    return;
                }
                String path = cursor.getString(0);
                long size = cursor.getLong(1);
                if (new File(path).exists()) {
                    createTrashItemLog(path, size);
                }
                int progressFile = calculateProgress(increaseProgress, index, count);
                mTrashHandler.updateProgress(progressFile, path);
                index++;
            }
        } catch (Exception e) {
            if (FeatureConfig.DEBUG_LOG) {
                LogHelper.e(SUB_TAG, "scanMediaLog", e);
            }
            return;
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
        if (FeatureConfig.DEBUG_LOG) {
            LogHelper.v(SUB_TAG, "doScan, scanMediaLog end! time = "
                    + (System.currentTimeMillis() - startTime));
        }
    }

    private void scanRoots(int increaseProgress) {
        if (mStopRequested) {
            if (FeatureConfig.DEBUG_LOG) {
                LogHelper.v(SUB_TAG, "doScan, mStopRequested, have not start,fileScanner return");
            }
            return;
        }
        if (FeatureConfig.DEBUG_LOG) {
            LogHelper.v(SUB_TAG, "doScan, scanRoots begin");
        }
        if (!TrashUtils.hasSdcard(mSdCardRootDirs)) {
            mTrashHandler.updateProgress(increaseProgress, null);
            for (TrashType t : mTrashTypes) {
                mTrashHandler.onTrashTypeFinish(t, mTypeSizeMap.get(t));
            }
            return;
        }
        if (mFileMatcher == null && mDirMatcher == null) {
            if (FeatureConfig.DEBUG_LOG) {
                LogHelper.v(SUB_TAG, "doScan, no matcher, fileScanner return");
            }
            // nothing to be scanned
            onProgressUpdate(null, increaseProgress);
            for (TrashType type : mTrashTypes) {
                mTrashHandler.onTrashTypeFinish(type, 0);
            }
            return;
        }
        final int N = mSdCardRootDirs.length;
        if (N == 0) {
            mTrashHandler.updateProgress(increaseProgress, null);
        }
        int[] progressRootDirs = calculateProgressByChildsCount(null, mSdCardRootDirs,
                increaseProgress);
        int progressDirs = 0;
        for (int i = 0; i < N; i++) {
            mCurRootDir = mSdCardRootDirs[i];
            File curDirFile = new File(mCurRootDir);

            if (progressRootDirs != null) {
                progressDirs = progressRootDirs[i];
            }

            String[] list = curDirFile.list();
            if (list == null || list.length == 0) { // may the path Permission
                // denied,so return null;
                mTrashHandler.updateProgress(progressDirs, null);
                if (i == (N - 1)) {
                    for (TrashType t : mTrashTypes) {
                        mTrashHandler.onTrashTypeFinish(t, mTypeSizeMap.get(t));
                    }
                }
                continue;
            }
            int j = 0;
            for (String firstLevelPath : list) {
                if (mStopRequested) {
                    if (FeatureConfig.DEBUG_LOG) {
                        LogHelper
                                .v(SUB_TAG,
                                        "doScan, mStopRequested, in scanning firstLevelPaths, fileScanner return");
                    }
                    return;
                }
                int progressFileList = calculateProgress(progressDirs, j, list.length);
                j++;
                if (FeatureConfig.DEBUG_LOG) {
                    LogHelper.i(SUB_TAG, "firstLevelPath=" + firstLevelPath + ",progressFileList="
                            + progressFileList + ",progressDirs=" + progressDirs
                            + ",(list.length+1)=" + (list.length + 1));
                }
                scanFile(new File(mCurRootDir, firstLevelPath), 1, progressFileList, mCurRootDir);
            }

            if (i == (N - 1)) {
                for (TrashType t : mTrashTypes) {
                    mTrashHandler.onTrashTypeFinish(t, mTypeSizeMap.get(t));
                }
            }
        }
        if (FeatureConfig.DEBUG_LOG) {
            LogHelper.v(SUB_TAG, "doScan, scanRoots end");
        }
    }

    public interface FileList {
        void listFile(File file);
    }

    private void onProgressUpdate(File f, int progress) {
        String desc = f == null ? "" : f.getAbsolutePath();
        mTrashHandler.updateProgress(progress, desc);
    }

    // 3级目录内，继续扫描子文件，3~6级目录允许扫描/Android/Data/下子文件
    private boolean shouldScanChildren(int level, String path, String rootPath) {
        if (level < DIRS_SCAN_LEVEL) {
            return true;
        } else {
            if (mAndroidDataFilePath != null && level < DIRS_SCAN_LEVEL_FOR_ANDROIDDATA) {
                if (path.toLowerCase().startsWith(mAndroidDataFilePath)) {
                    return true;
                }
            }
            if (level < DIRS_SCAN_LEVEL_FOR_TENCENT) { // tenect path scan 6
                // subpath
                return path.toLowerCase().startsWith(
                        new File(rootPath, "/tencent").getAbsolutePath());
            }
        }
        return false;
    }

    private void scanFile(File scanningFile, int level, int increaseProgress, String currentRootDir) {
        if (mStopRequested) {
            return;
        }
        mCurScanLev = level;
        if (scanningFile.isDirectory()) {
            if (mDirMatcher != null) {
                mDirMatcher.match(scanningFile);
            }
            File[] subFiles = scanningFile.listFiles();
            if (subFiles != null && subFiles.length > 0) {
                final int N = subFiles.length;
                for (int i = 0; i < N; i++) {
                    int progressSubFiles = calculateProgress(increaseProgress, i, N);
                    scanFile(subFiles[i], level + 1, progressSubFiles, currentRootDir);
                }
            } else {
                onProgressUpdate(scanningFile, increaseProgress);
            }
        } else {
            if (mFileMatcher != null) {
                mFileMatcher.match(scanningFile);
            }
            onProgressUpdate(scanningFile, increaseProgress);
        }
    }

    private TrashItem createTrashItemEmptyFolder(String path, long size) {
        TrashItem trash = new TrashItem();
        trash.trashType = TrashType.EMPTY_FOLDER;
        trash.filePath = path;
        trash.isDir = true;
        trash.size = size;
        trash.fileCount = 1;
        mTypeSizeMap.put(TrashType.EMPTY_FOLDER, mTypeSizeMap.get(TrashType.EMPTY_FOLDER)
                + trash.size);
        if (!mStopRequested) {
            mTrashesData.putTrash(TrashType.EMPTY_FOLDER, trash);
        }
        if (FeatureConfig.DEBUG_LOG) {
            LogHelper.v(SUB_TAG, "[trash_empoty_folder] filePath:" + trash.filePath + " size:"
                    + trash.size);
        }
        return trash;
    }

    private TrashItem createTrashItemLog(String path, long size) {
        TrashItem trash = new TrashItem();
        trash.trashType = TrashType.LOG_FILE;
        trash.filePath = path;
        trash.fileCount = 1;
        trash.size = size;
        mTypeSizeMap.put(TrashType.LOG_FILE, mTypeSizeMap.get(TrashType.LOG_FILE) + trash.size);
        if (!mStopRequested) {
            mTrashesData.putTrash(TrashType.LOG_FILE, trash);
        }
        if (FeatureConfig.DEBUG_LOG) {
            LogHelper.v(SUB_TAG, "[trash_log] filePath:" + trash.filePath + " size:" + trash.size);
        }
        return trash;
    }

    private TrashItem createTrashItemApk(File f, String absolutePath) {
        TrashItem trash = null;
        ApkFileInfo apkInfo = ApkFileManager.getInstance().getApkInfo(absolutePath);
        if (apkInfo != null) {
            trash = apkInfo.createApkFileItem();
        }
        if (FeatureConfig.DEBUG_LOG) {
            LogHelper.i(SUB_TAG, "createTrashItemApk trash=" + trash);
        }
        if (trash == null) {
            return null;
            // trash = parseApkFile(f);
        }
        mTypeSizeMap.put(TrashType.APK_FILE, mTypeSizeMap.get(TrashType.APK_FILE) + trash.size);
        if (!mStopRequested) {
            mTrashesData.putTrash(TrashType.APK_FILE, trash);
        }
        if (FeatureConfig.DEBUG_LOG) {
            LogHelper.v(SUB_TAG, "[trash_unless] filePath:" + absolutePath);
        }
        return trash;
    }

    private TrashItem createTrashItemTemp(String path, long size) {
        TrashItem trash = new TrashItem();
        trash.trashType = TrashType.TEMP_FILE;
        trash.filePath = path;
        trash.size = size;
        trash.fileCount = 1;
        mTypeSizeMap.put(TrashType.TEMP_FILE, mTypeSizeMap.get(TrashType.TEMP_FILE) + trash.size);
        if (!mStopRequested) {
            mTrashesData.putTrash(TrashType.TEMP_FILE, trash);
        }
        if (FeatureConfig.DEBUG_LOG) {
            LogHelper.v(SUB_TAG, "[trash_temp] filePath:" + trash.filePath + " size:" + trash.size);
        }
        return trash;
    }

    private TrashItem createTrashItemLargeFile(File f, String path, long size, long id) {
        FileTrashItem trash = new FileTrashItem();
        trash.id = id;
        trash.trashType = TrashType.LARGE_FILE;
        trash.mFileName = f.getName();
        trash.lastModified = f.lastModified();
        trash.mFileType = TrashMimeTypes.getFileTypes(trash.mFileName);
        trash.filePath = path;
        trash.fileCount = 1;
        trash.size = size;
        trash.isSelectedDefault = trash.isSelected = false;
        mTypeSizeMap.put(TrashType.LARGE_FILE, mTypeSizeMap.get(TrashType.LARGE_FILE) + trash.size);
        if (!mStopRequested) {
            mTrashesData.putTrash(TrashType.LARGE_FILE, trash);
        }
        if (FeatureConfig.DEBUG_LOG) {
            LogHelper.v(SUB_TAG, "[trash_large] filePath:" + trash.filePath + " size:" + size);
        }
        return trash;
    }
}
